package com.example.security.sso;

import com.example.security.entity.SysUser;
import com.example.security.exception.InvalidTicketException;
import com.example.security.mapper.UserMapper;
import com.example.security.service.JwtService;
import com.example.security.service.SessionSyncService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * SSO认证中心
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SsoAuthenticationCenter {
    
    private final RedisTemplate<String, Object> redisTemplate;
    private final JwtService jwtService;
    private final SessionSyncService sessionSyncService;

    private final UserMapper userMapper;
    private final PasswordEncoder passwordEncoder;

    public SysUser authenticate(String username, String password) {
        SysUser user = userMapper.selectByUsername(username);
        if (user == null || !passwordEncoder.matches(password, user.getPassword())) {
            throw new BadCredentialsException("用户名或密码错误");
        }
        return user;
    }

    public void registerClient(String clientId, String clientUrl) {
        redisTemplate.opsForHash().put("sso:clients", clientId, clientUrl);
    }
    /**
     * 生成SSO票据
     */
    public String generateTicket(String token) {
        if (!StringUtils.hasText(token)) {
            throw new IllegalArgumentException("Token不能为空");
        }
        
        try {
            String ticket = UUID.randomUUID().toString();
            // 存储ticket与token的关系
            redisTemplate.opsForValue().set(getTicketKey(ticket), token, 5, TimeUnit.MINUTES);
            log.debug("生成ticket成功: {}", ticket);
            return ticket;
        } catch (Exception e) {
            log.error("生成ticket失败", e);
            throw new RuntimeException("生成ticket失败", e);
        }
    }
    
    /**
     * 验证SSO票据
     */
    public String validateTicket(String ticket) {
        if (!StringUtils.hasText(ticket)) {
            throw new InvalidTicketException("Ticket不能为空");
        }
        
        try {
            String token = (String) redisTemplate.opsForValue().get(getTicketKey(ticket));
            if (token == null) {
                throw new InvalidTicketException("无效的ticket");
            }
            
            // 验证成功后删除ticket
            redisTemplate.delete(getTicketKey(ticket));
            log.debug("验证ticket成功: {}", ticket);
            return token;
        } catch (InvalidTicketException e) {
            throw e;
        } catch (Exception e) {
            log.error("验证ticket失败: " + ticket, e);
            throw new RuntimeException("验证ticket失败", e);
        }
    }
    
    /**
     * 通知客户端注销
     */
    public void notifyClientLogout(String username) {
        try {
            // 获取用户所有的客户端会话
            String sessionKey = "sso:session:" + username;
            Set<Object> clientIds = redisTemplate.opsForHash().keys(sessionKey);
            
            // 通知每个客户端
            for (Object clientId : clientIds) {
                try {
                    String token = sessionSyncService.getClientToken(username, clientId.toString());
                    if (token != null) {
                        // 将token加入黑名单
                        addToBlacklist(token);
                        log.debug("通知客户端注销成功: username={}, clientId={}", username, clientId);
                    }
                } catch (Exception e) {
                    log.error("通知客户端注销失败: username=" + username + ", clientId=" + clientId, e);
                }
            }
            
            // 清除会话信息
            sessionSyncService.removeSession(username);
        } catch (Exception e) {
            log.error("通知客户端注销失败: username=" + username, e);
            throw new RuntimeException("通知客户端注销失败", e);
        }
    }
    
    /**
     * 将token加入黑名单
     */
    private void addToBlacklist(String token) {
        try {
            String blacklistKey = "token:blacklist:" + token;
            redisTemplate.opsForValue().set(blacklistKey, "", 24, TimeUnit.HOURS);
        } catch (Exception e) {
            log.error("添加token到黑名单失败: " + token, e);
        }
    }
    
    /**
     * 检查token是否在黑名单中
     */
    public boolean isTokenBlacklisted(String token) {
        try {
            String blacklistKey = "token:blacklist:" + token;
            return Boolean.TRUE.equals(redisTemplate.hasKey(blacklistKey));
        } catch (Exception e) {
            log.error("检查token黑名单失败: " + token, e);
            return false;
        }
    }
    
    private String getTicketKey(String ticket) {
        return "sso:ticket:" + ticket;
    }
} 